// Copyright (C) Calum Grant 2008

#include "cppscript"
#include "dynamic/extensions.hpp"

#include <vector>
#include <sstream>
#include <cstring>


dynamic::pickler::pickler(std::ostream & output) : 
	m_output(output) 
{ 
}


std::string dynamic::pickle(const var & v)
{
	std::ostringstream ss;
	pickler p(ss);
	p.write_object(v);
	return ss.str();
}


var dynamic::unpickle(const std::string & s)
{
	std::istringstream ss;
	ss.rdbuf()->str(s);

	return unpickler(ss).read_object();
}


void var_impl::pickle(pickler & p)
{
	throw not_supported(this, "pickle");
}


void var_impl::unpickle(unpickler & u)
{
	throw not_supported(this, "unpickle");
}


void dynamic::pickler::write_object(const var & o)
{
	shared_var_impl * shared = o.impl().shared_var();

	if(shared)
	{
		object_map::iterator i = m_objects.find(shared);
		if(i == m_objects.end())
		{
			std::size_t id = m_objects.size();
			m_objects[shared] = id;
			shared->pickle(*this);
		}
		else
		{
			// Exists already
			write_object_type(cmp_reference);
			write_int(int(i->second));
		}
	}
	else
	{
		o.impl().pickle(*this);
	}
}


void dynamic::pickler::write_object_type(var_cmp_index code)
{
	write_int(code+'A');
}


void dynamic::pickler::write_int(int i)
{
	if(i>0 && i<128 && isalpha(i)) 
		m_output << char(i);
	else
		m_output << i << ';';
}


void dynamic::pickler::write_size(std::size_t i)
{
	m_output << i << ';';
}


void dynamic::pickler::write_double(double d)
{
	m_output << d << ';';
}


dynamic::unpickler::unpickler(std::istream & input) : m_input(input)
{
}


var dynamic::unpickler::read_object()
{
	var_cmp_index type = var_cmp_index(read_int()-'A');
	var result;

	switch(type)
	{		
	case cmp_reference:
		{
			std::size_t id = read_int();
			if(id>=m_objects.size()) throw invalid_string();
			return m_objects[id];
		}
		break;

	case cmp_function_0:
		return (f0)read_function_pointer();

	case cmp_function_v0:
		return (vf0)read_function_pointer();

	case cmp_function_1:
		return (f1)read_function_pointer();

	case cmp_function_v1:
		return (vf1)read_function_pointer();

	case cmp_function_2:
		return (f2)read_function_pointer();

	case cmp_function_v2:
		return (vf2)read_function_pointer();

	case cmp_function_3:
		return (f3)read_function_pointer();

	case cmp_function_v3:
		return (vf3)read_function_pointer();

	case cmp_function_4:
		return (f4)read_function_pointer();

	case cmp_function_v4:
		return (vf4)read_function_pointer();

	case cmp_function_5:
		return (f5)read_function_pointer();

	case cmp_function_v5:
		return (vf5)read_function_pointer();

	case cmp_function_6:
		return (f6)read_function_pointer();

	case cmp_function_v6:
		return (vf6)read_function_pointer();
	case cmp_function_7:
		return (f7)read_function_pointer();

	case cmp_function_v7:
		return (vf7)read_function_pointer();

	case cmp_function_8:
		return (f8)read_function_pointer();
	case cmp_function_v8:
		return (vf8)read_function_pointer();

	case cmp_function_9:
		return (f9)read_function_pointer();

	case cmp_function_v9:
		return (vf9)read_function_pointer();

	case cmp_function_10:
		return (f10)read_function_pointer();

	case cmp_function_v10:
		return (vf10)read_function_pointer();

	case cmp_bind:
		result = internal::bind(read_int());
		break;

	case cmp_dispatcher:
		result = dispatcher(null);
		break;

	default:
		result = unpickle_type(type);
	}

	shared_var_impl * sh = result.impl().shared_var();
	if(sh) m_objects.push_back(sh);
	result.impl().unpickle(*this);

	return result;
}


void * dynamic::unpickler::read_function_pointer()
{
	return internal::lookup_function( read_string().c_str() );
}


int dynamic::unpickler::read_int()
{
	if(isalpha(m_input.peek()))
	{
		char x;
		m_input >> x;
		return x;
	}
	else
	{
		char c;
		int x;
		m_input >> x >> c;
		return x;
	}
}


std::size_t dynamic::unpickler::read_size()
{
	char c;
	std::size_t x;
	m_input >> x >> c;
	return x;
}


double dynamic::unpickler::read_double()
{
	double x;
	char c;
	m_input >> x >> c;
	return x;
}


void dynamic::pickler::write_string(std::string const & str)
{
	write_int(int(str.size()));
	for(std::string::const_iterator i=str.begin(); i!=str.end(); ++i)
		write_int(*i);
}


std::string dynamic::unpickler::read_string()
{
	std::string result;
	result.resize(read_int());
	for(std::string::iterator i=result.begin(); i!=result.end(); ++i)
		*i = read_int();
	return result;
}


namespace
{
	class function_lut
	{
		function_lut() { }

		struct name_comparator
		{
			bool operator()(const char *a, const char *b) const { return strcmp(a,b)<0; }
		};

		// Could use bidirectional_map here.
		std::map<void*, const char*> m_function_name;
		std::map<const char*, void*, name_comparator> m_function_address;

	public:

		static function_lut & singleton()
		{
			static function_lut lut;	// Yuck.  Doesn't need to be threadsafe.
			return lut;
		}

		bool add(const char * name, void * address)
		{
			if( m_function_address.find(name) != m_function_address.end())
				std::cerr << "Warning: function name " << name << " is registered twice" << std::endl;
			m_function_name[address]=name;
			m_function_address[name]=address;
			return true;
		}

		void * lookup(const char * name) const 
		{
			std::map<const char*, void*, name_comparator>::const_iterator i=m_function_address.find(name);
			if( i == m_function_address.end() ) throw exception("function not found");
			return i->second;
		}

		const char * lookup(void * address) const
		{
			std::map<void*, const char*>::const_iterator i=m_function_name.find(address);
			if( i == m_function_name.end() ) throw exception("function not found");
			return i->second;
		}
	};
}


bool dynamic::internal::register_function(const char * name, void * address)
{
	return function_lut::singleton().add(name, address);
}


const char * dynamic::internal::lookup_function(void * address)
{
	return function_lut::singleton().lookup(address);
}


void * dynamic::internal::lookup_function(const char * name)
{
	return function_lut::singleton().lookup(name);
}


namespace
{
	class pickle_types
	{
		std::map<var_cmp_index, var> m_types;

		pickle_types() { }

	public:
		static pickle_types & singleton()
		{
			static pickle_types s;
			return s;
		}

		void add(var_cmp_index i, const var&t)
		{
			m_types[i] = t;
		}

		var get(var_cmp_index i) const
		{
			std::map<var_cmp_index, var>::const_iterator it = m_types.find(i);

			// Should be thread-safe and cross-apartment.
			if(it == m_types.end()) throw invalid_string();

			return +it->second;
		}
	};
}


void dynamic::register_pickle_type(var_cmp_index i, const var & t)
{
	pickle_types::singleton().add(i,t);
}


var dynamic::unpickle_type(var_cmp_index i)
{
	return pickle_types::singleton().get(i);
}
